﻿using OperationPlugins.Compositions;
using OperationPlugins.Reflections;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.Composition;

namespace OperationPlugins.Validators
{
    /// <summary>
    /// Provides information about a validator.
    /// </summary>
    internal sealed class ValidatorInfo : PluginInfo
    {
        private static readonly ConcurrentDictionary<Type, List<ValidatorInfo>> AllByType = new ConcurrentDictionary<Type, List<ValidatorInfo>>();
        private static readonly ConcurrentDictionary<Type, List<ValidatorInfo>> ValidatorsByExact = new ConcurrentDictionary<Type, List<ValidatorInfo>>();
        private static readonly ConcurrentDictionary<Type, List<ValidatorInfo>> ValidatorsByInherited = new ConcurrentDictionary<Type, List<ValidatorInfo>>();
        private readonly List<Type> ExactTargetTypes = new List<Type>();
        private readonly List<Type> InheritedTargetTypes = new List<Type>();

        /// <summary>
        /// The validation level
        /// </summary>
        internal ValidationLevel ValidationLevel { set; get; }

        /// <summary>
        /// No public constructor available, use the CreateInstance method.
        /// </summary>
        private ValidatorInfo()
        {            
        }

        /// <summary>
        /// Gets validators by specified validation level. 
        /// 
        /// Validators should be invoked in this order!
        /// </summary>
        /// <param name="target">The object to be validated.</param>
        /// <param name="context">Provides information about and functionality for the current operation.</param>
        /// <param name="level">Validation level.</param>
        /// <returns>Validators.</returns>
        internal static IEnumerable<ValidatorInfo> GetValidators(object target, OperationContext context, ValidationLevel level)
        {
            foreach (ValidatorInfo info in GetAll(target.GetType()))
            {
                if (info.ValidationLevel == level && info.IsValidFor(context.Operation) && info.IsValidFor(context.Branch))
                {
                    yield return info;
                }
            }
        }

        /// <summary>
        /// Returns validators for the specified target type.
        /// </summary>
        /// <param name="targetType">The target type.</param>
        /// <returns>Validators for the specified target type.</returns>
        private static IEnumerable<ValidatorInfo> GetAll(Type targetType)
        {
            List<ValidatorInfo> items;

            if (AllByType.TryGetValue(targetType, out items) == false)
            {
                items = new List<ValidatorInfo>();

                List<ValidatorInfo> infos;

                // Get validators matching the target type.
                if (ValidatorsByExact.TryGetValue(targetType, out infos))
                {
                    items.AddRange(infos);
                }

                // Get validators matching the target type.
                foreach (Type validatorType in ValidatorsByInherited.Keys)
                {
                    if (validatorType.IsAssignableFrom(targetType))
                    {
                        items.AddRange(ValidatorsByInherited[validatorType]);
                    }
                }

                AllByType.TryAdd(targetType, items);
            }

            return items;
        }

        /// <summary>
        /// Creates a new ValidatorInfo object.
        /// </summary>
        /// <param name="pluginType">The plugin type.</param>
        /// <returns>A new ValidatorInfo object.</returns>
        private static void CreateInstance(Type pluginType)
        {
            var definition = pluginType.GetAttribute<ValidatorOfAttribute>();

            var info = new ValidatorInfo();

            info.PluginType = pluginType;
            info.ValidationLevel = definition.ValidationLevel;

            // Resolve target types.
            foreach (var targetType in definition.TargetTypes)
            {
                if (definition.Inherited)
                {
                    if (info.InheritedTargetTypes.Contains(targetType) == false)
                    {
                        info.InheritedTargetTypes.Add(targetType);
                    }
                }
                else
                {
                    if (info.ExactTargetTypes.Contains(targetType) == false)
                    {
                        info.ExactTargetTypes.Add(targetType);
                    }
                }
            }

            // Resolve operation filters.
            foreach (var filter in pluginType.GetAttributes<OperationFilterAttribute>())
            {
                var operation = new OperationId(filter.Value);
                if (info.OperationFilters.Contains(operation) == false)
                {
                    info.OperationFilters.Add(operation);
                }
            }

            // Resolve branch filters.
            foreach (var filter in pluginType.GetAttributes<BranchFilterAttribute>())
            {
                var branch = new BranchId(filter.Value);
                if (info.BranchFilters.Contains(branch) == false)
                {
                    info.BranchFilters.Add(branch);
                }
            }

            foreach (Type targetType in info.ExactTargetTypes)
            {
                List<ValidatorInfo> infos;
                if (ValidatorsByExact.TryGetValue(targetType, out infos) == false)
                {
                    ValidatorsByExact.TryAdd(targetType, infos = new List<ValidatorInfo>());
                }
                infos.Add(info);
            }

            foreach (Type targetType in info.InheritedTargetTypes)
            {
                List<ValidatorInfo> infos;
                if (ValidatorsByInherited.TryGetValue(targetType, out infos) == false)
                {
                    ValidatorsByInherited.TryAdd(targetType, infos = new List<ValidatorInfo>());
                }
                infos.Add(info);
            }
        }

        /// <summary>
        /// Initialize. 
        /// </summary>
        [Export(typeof(IInitialize))]
        private sealed class Initializer : IInitialize
        {
            void IInitialize.Initialize(InitializationContext context)
            {
                foreach (var plugin in context.CompositionContainer.GetPlugins<IValidator>())
                {
                    if (plugin.GetType().HasAttribute<ValidatorOfAttribute>())
                    {
                        CreateInstance(plugin.GetType());
                    }
                }
            }
        }

    }
}